#include "io_com.h"
#include "QLogTool.h"
#include <vector>
#include <sstream>

using namespace std;

io_COM :: io_COM(){
    thread_rst();
}

io_COM :: ~io_COM(){
    io_COM_echo_hardRST(true);
    COM_close();
    delete COM;
}

void io_COM::thread_rst(){
    tEnableLOG = true;
    sig_openCOMflag = false;
    sig_closeCOMflag = false;
    sig_BtSendFlag = false;
    sig_DataReadyFlag = false;
    tComErr = false;
    clear();
    sta = closed;
}

void io_COM::thread_fatal(QString str){
    thread_rst();
    sta = error;
    str = "oops! COM: " + str;
    LOG(str);
}

void io_COM::run(){
    LOG("Start running.");
    unsigned long sleep_ms = 10;
    thread_rst();
    COM = new QSerialPort();
    qRegisterMetaType<QSerialPort::SerialPortError>("QSerialPort::SerialPortError");
    connect(COM,SIGNAL(error(QSerialPort::SerialPortError)),this,SLOT(io_COM_echo_error(QSerialPort::SerialPortError)));

    while (true) {
        switch (sta) {
        case closed:
            if(sig_openCOMflag){
                sig_openCOMflag = false;
                sta = opening;
            }else{
                thread_rst();
            }
            break;
        case opening:
            if (COM_open(sig_openCOMname)) {
                sta = ready;
            } else {
                LOGBox("FATAL","Open " + COM->portName() + " Failed.");
                sta = closed;
            }
            break;
        case ready:
            if(sig_closeCOMflag){
                sig_closeCOMflag = false;
                COM_close();
                sta = closed;
            }else if(sig_DataReadyFlag){
                sig_DataReadyFlag = false;
                sta = procing;
            }else if(sig_txFlag){
                sig_txFlag = false;
                io_COM_tx(tx_buffer_pool);
            }else if(tComErr){
                tComErr = false;
                sta = closed;
            }
            break;
        case txing:
            //io_COM_tx();
            break;
        case procing:
            if(proced_flag){
                proced_flag = false;
                rx_buffer.remove(0,rmove_rx_cnt);
                sta = ready;
            }
            break;
        case error:
            sleep_ms = 1000;
            break;
        case reserve:
            sta = closed;
            break;


        }

        // RecvPortProc();
        // RefreshComInfo();
        msleep(sleep_ms);
    }
}

bool io_COM::COM_open(QString portname){
    COM->setBaudRate(QSerialPort::Baud115200);
    COM->setStopBits(QSerialPort::OneStop);
    COM->setDataBits(QSerialPort::Data8);
    COM->setParity(QSerialPort::NoParity);
    COM->setPortName(portname);
    COM->setReadBufferSize(10240);

    /* exception: port has been opened before */
    if(COM->isOpen()) COM->close();

    /* now open the port */
    if(COM->open(QIODevice::ReadWrite)){
        // clear buffer
        COM->readAll();
        io_COM_echo_hardRST(true);
        LOG(COM->portName() + " open succeed");
        return true;
    }else{
        LOG(COM->portName() + " open falied");
        return false;
    }

}

void io_COM::COM_close(){
    COM->close();
    LOG(COM->portName() + " now closed.");
}

void io_COM::RefreshComInfo() {
    auto currComInfo = QSerialPortInfo::availablePorts();
    if (m_comInfoHistory.size() == currComInfo.size()) {
        return;
    }
    LOG("Com Changed from %u -> %u",  m_comInfoHistory.count(), currComInfo.count());
    m_comInfoHistory = currComInfo;
    m_IOCollection.clear();
    for (const auto& o : m_comInfoHistory) {
        string comName = o.portName().toStdString();
        string comInfo = o.description().toStdString();
        m_IOCollection[comName] = comInfo;
    }
}

QString io_COM::ioINFO::getInfo(){
    QString dst;
    dst += ("proced: " + QString::number(proced) + "\t,");
    dst += ("bufsize: " + QString::number(bufsize) + "\t");

    return dst;
}

void io_COM::ioINFO::clear(){
    bufsize = 0;
    proced = 0;
    err = 0;
    speed = 0;
    mdelay = 0;
    ratio = 0;
}

void io_COM::clear(){
    rx_buffer.clear();
    tx_buffer_pool.clear();
    rxINFO.clear();
    txINFO.clear();
}

void io_COM::io_COM_tx(QByteArray src){
    if(COM->isOpen()){
        COM->write(src);
        COM->waitForBytesWritten();
        txINFO.bufsize += src.count();
        emit io_COM_txDone();
    }else{
        thread_fatal("[" + COM->portName() + "] is not opened");
    }
}


QString io_COM::sta2str(com_sta sta){
    map<com_sta, string> staNameMap = {
        {closed,   "closed"},
        {ready,    "ready"},
        {opening,  "opening"},
        {txing,    "txing"},
        {error,    "error"},
        {procing,  "procing"},
    };
    string str = staNameMap.find(sta) == staNameMap.end() ? ("unknown:" + to_string(sta)) : staNameMap[sta];
    return QString::fromStdString(str);
}


vector<uint8_t> io_COM::RecvPortData()
{
    vector<uint8_t> ret;
    if((COM == nullptr) || (COM->isOpen()==false)){
        return ret;
    }
    if(COM->waitForReadyRead(1) == false){ // 在调用接口的线程中延时
        return ret;
    }
    if (COM->bytesAvailable() == 0) {
        return ret;
    }
    QByteArray qRet = COM->readAll();

    return vector<uint8_t>(qRet.begin(), qRet.end());
}

void io_COM::io_COM_echo_error(QSerialPort::SerialPortError info){
    if(info == QSerialPort::SerialPortError::ResourceError){
        LOG("io_COM_echo_error[%u]", info);
        tComErr = true;
    }
}

void io_COM::io_COM_echo_hardRST(bool tHigh){
    tHigh = !tHigh;
    if(COM->isOpen()){
#ifdef io_COM_ENABLE_HARD_RST_via_DTR_pin
        COM->setDataTerminalReady(tHigh);
        LOG("RST pin set to %u", tHigh);
#elif
        LOOG("Hard rst disabled");
#endif
    }
}

void io_COM::io_COM_echo_procDone(int proced_cnt){
    proced_flag = true;
    rmove_rx_cnt = proced_cnt;
}
void io_COM::io_COM_echo_txDoneDelay(int ms){
    txINFO.mdelay = ms;
}
void io_COM::io_COM_echo_rxDoneDelay(int ms){
    rxINFO.mdelay = ms;
}

void io_COM::io_COM_echo_tx(QByteArray src){
    sig_txFlag = true;
    tx_buffer_pool = src;
}

void io_COM::io_COM_echo_open(QString portInfo){
    sig_openCOMflag = true;
    stringstream ss(portInfo.toStdString());
    string portName;
    std::getline(ss, portName, ':');
    sig_openCOMname = QString::fromStdString(portName);
}
void io_COM::io_COM_echo_close(){
    sig_closeCOMflag = true;
}
void io_COM::io_COM_echo_send(){
    sig_BtSendFlag = true;
}
